package com.buddy.learn.spring.mvc.template;

import com.buddy.learn.spring.mvc.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class BDispatcherServlet extends HttpServlet {

    //保存Application.properties配置文件中的内容
    private Properties contextConfig = new Properties();
    //保存扫描到的所有类名
    private List<String> classNames = new ArrayList<>();
    //IOC容器
    private Map<String, Object> ioc = new HashMap<>();
    //保存URL和Method的对应关系
//    private Map<String, Method> handlerMapping = new HashMap<>();

    //思考为什么不用Map<String,HandlerMapping>？
    //map的key只能是url，HandlerMapping本身的功能就是把method和url对应\\已经具备了map的功能
    //根据设计原则，冗余了，违背了单一职责、最小知道原则
    private List<BHandlerMapping> handlerMapping = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        //6.调用，运行阶段
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exception,Detail:" + Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {

        BHandlerMapping handlerMapping = getHandler(req);
        if (handlerMapping == null) {
            resp.getWriter().write("404 not found!!!!");
            return;
        }
        //获得方法的形参列表
        Class<?>[] paramTypes = handlerMapping.getParamTypes();

        //实参列表
        Object[] paramValues = new Object[paramTypes.length];

        Map<String, String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> param : params.entrySet()) {
            String value = Arrays.toString(param.getValue())
                    .replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", ",");
            if (!handlerMapping.paramIndexMapping.containsKey(param.getKey())) {
                continue;
            }
            int index = handlerMapping.paramIndexMapping.get(param.getKey());
            paramValues[index] = convert(paramTypes[index], value);
        }

        if (handlerMapping.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            int reqIndex = handlerMapping.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
        }

        if (handlerMapping.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            int respIndex = handlerMapping.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;
        }

       Object returnValue = handlerMapping.getMethod().invoke(handlerMapping.controller,paramValues);
        if(returnValue == null || returnValue instanceof Void){
            return;
        }
        resp.getWriter().write(returnValue.toString());
    }

    private BHandlerMapping getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) return null;
        //绝对路径
        String url = req.getRequestURI();
        //处理成相对路径；
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
        for (BHandlerMapping mapping : this.handlerMapping) {
            if (mapping.getUrl().equals(url)) {
                return mapping;
            }
        }
        return null;

    }


    //url传递过来的参数都是String类型的，HTTP是基于字符串协议
    //只需要把String转换为对应类型
    private Object convert(Class<?> type, String value) {
        if (Integer.class == type) {
            return Integer.valueOf(value);
        }else if (Double.class == type){
            return Double.valueOf(value);
        }
        //如果还有其他类型，考虑使用策略模式了
        return value;
    }


    //初始化阶段
    @Override
    public void init(ServletConfig config) {
        //1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2.扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));
        //3.初始化扫描到的类，并放入到IOC容器
        doInstance();
        //4.完成依赖注入
        doAutowired();
        //5.初始化HandlerMapping
        initHandlerMapping();
        System.out.println("Buddy Spring Framework is inited...");
    }

    //初始化url和Method
    private void initHandlerMapping() {
        //
        if (ioc.isEmpty()) return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(BController.class)) continue;

            //保存类上面的@BRequestMapping("/")
            String baseUrl = "";
            if (clazz.isAnnotationPresent(BRequestMapping.class)) {
                BRequestMapping requestMapping = clazz.getAnnotation(BRequestMapping.class);
//                baseUrl = requestMapping.value();
                baseUrl = requestMapping.value()[0];
            }

            //默认获取所有的public方法
            for (Method method : clazz.getMethods()) {
                if (method.isAnnotationPresent(BRequestMapping.class)) continue;
                BRequestMapping requestMapping = method.getAnnotation(BRequestMapping.class);
                String url = "/" + baseUrl + "/" + requestMapping.value()[0]
                        .replaceAll("/+", "/");
                this.handlerMapping.add(new BHandlerMapping(url, entry.getValue(), method));
                System.out.println("Mapped:" + url + "," + method);
            }

        }

    }

    //自动依赖注入
    private void doAutowired() {
        if (ioc.isEmpty()) return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //Declared所有的，特定的字段，包括private/protected/default
            //正常来说，普通的OOP编程只能获取public属性
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for (Field field : fields) {
                if (field.isAnnotationPresent(BAutowired.class)) continue;
                BAutowired autowired = field.getAnnotation(BAutowired.class);

                //如果用户没有自定义beanName，默认根据类型注入
                //省去了对类名首字母的情况的判断
                String beanName = autowired.value().trim();
                if ("".equals(beanName)) {
                    //获取接口的类型
                    beanName = field.getType().getName();//类型注入
                }

                //如果是public以外的修饰，只要加了@Autowired注解，都要强制赋值
                //反射中叫做暴力访问
                field.setAccessible(true);

                try {
                    //用反射机制，动态给字段赋值
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doInstance() {
        //初始化为DI做准备
        if (classNames.isEmpty()) return;
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                //什么类需要初始化？
                //加了注解的类
                if (clazz.isAnnotationPresent(BController.class)) {
                    Object instance = clazz.newInstance();
                    //Spring 默认 key className首字母小写
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                } else if (clazz.isAnnotationPresent(BService.class)) {
                    //1.默认类名首字母小写
                    //2.自定义的beanName
                    //3.根据类型自动赋值
                    BService service = clazz.getAnnotation(BService.class);
                    String beanName = service.value();
                    if ("".equals(beanName.trim())) {
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);
                    for (Class<?> i : clazz.getInterfaces()) {
                        if (ioc.containsKey(i.getName())) {
                            throw new Exception("The " + i.getName() + " is exists！！");
                        }
                        //把接口的类型直接作为key
                        ioc.put(i.getName(), clazz.newInstance());
                    }

                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //如果类名本身是小写字母，会出问题
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        //大小写字母的ASCII码相差32，而且大写字母的ASCII码小于小写的ASCII码
        //JAVA中对char做算学运算，实际是对ASCII码做算学运算
        chars[0] += 32;
        return String.valueOf(chars);
    }

    //扫描出相关的类
    private void doScanner(String scanPackage) {
        //scanPackage=com.buddy.learn.spring.mvc.demo
        //转换为文件路径，将.替换为/
        //classPath
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replace("\\.", "/"));
        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }


    /**
     * 加载配置文件
     *
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        //从类路径下找到Spring主配置文件所在的路径
        //并将其读取出来放到内存中
        try (InputStream fis =
                     this.getClass().getClassLoader()
                             .getResourceAsStream(contextConfigLocation)) {
            contextConfig.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //保存一个url和method的关系
    public class BHandlerMapping {
        //必须把url放到HandlerMapping
        private String url;
        private Method method;
        private Object controller;
        //保存形参列表
        //参数名字作为key，參數的順序作爲value
        private Map<String, Integer> paramIndexMapping;
        private Class<?>[] paramTypes;//保存形参列表

        public BHandlerMapping(String url, Object controller, Method method) {
            this.url = url;
            this.method = method;
            this.controller = controller;
            this.paramTypes = method.getParameterTypes();
            this.paramIndexMapping = new HashMap<>();
            putParamIndexMapping(method);
        }

        public String getUrl() {
            return url;
        }

        public Method getMethod() {
            return method;
        }

        public Object getController() {
            return controller;
        }

        public Class<?>[] getParamTypes() {
            return paramTypes;
        }

        private void putParamIndexMapping(Method method) {

            //获取方法的所有注解，把方法上的注解获取到
            //得到的是一个二维数组，因为一个字段可以加多个注解，一个方法上又有多个字段
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            for (int i = 0; i < parameterAnnotations.length; i++) {
                for (Annotation a : parameterAnnotations[i]) {
                    //只解析BRequestParam
                    if (a instanceof BRequestParam) {
                        //获取到BRequestParam注解的参数名称
                        String paramName = ((BRequestParam) a).value();

                        //根据参数名称，去http://localhost:8080/demo/query?name=Tom匹配
                        if ("".equals(paramName.trim()))
                            paramIndexMapping.put(paramName, i);
                    }
                }
            }

            //提取方法中的request和response参数
            Class<?>[] paramsTypes = method.getParameterTypes();
            for (int i = 0; i < paramsTypes.length; i++) {
                Class<?> type = paramsTypes[i];
                if (type == HttpServletResponse.class
                        || type == HttpServletRequest.class) {
                    paramIndexMapping.put(type.getName(), i);
                }
            }
        }

    }
}
